-
САБ
-
-
Вне сайта
-
Gold Boarder
-
- Сообщений: 200
- Спасибо получено 13
-
-
|
Возьмем для примера мегу88, синус будем генерить на выходе OC0A.
Итак, делаем массив значений ШИМ на 1 период синуса. Индекс массива соответствует фазе синуса. Если массив будет состоять из 256 значений, то значение индекса 0 соответствует фазе 0, значение индекса 64 соответствует фазе Пи/2, 128 - Пи, 256 - 2*Пи. Если мы будем использовать байтовую переменную в качестве индекса массива, нам не надо будет следить за ее выходом за пределы массива, при фазе 2*Пи она станет равной 0.
Значения ШИМ у нас могут меняться от 0 до 0xFF, поэтому и значения в массиве должны быть в этом диапазоне:
#include <stdint.h>
#include <avr/pgmspace.h>
uint8_t PROGMEM sin_table[]
{
0x7F, 0x7C, 0x79, 0x76, 0x73, 0x6F, 0x6C, 0x69, 0x66, 0x63, 0x60, 0x5D, 0x5A, 0x57, 0x54, 0x51,
0x4E, 0x4C, 0x49, 0x46, 0x43, 0x40, 0x3E, 0x3B, 0x38, 0x36, 0x33, 0x31, 0x2E, 0x2C, 0x2A, 0x27,
0x25, 0x23, 0x21, 0x1F, 0x1D, 0x1B, 0x19, 0x17, 0x15, 0x14, 0x12, 0x10, 0x0F, 0x0E, 0x0C, 0x0B,
0x0A, 0x09, 0x07, 0x06, 0x05, 0x05, 0x04, 0x03, 0x02, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x02, 0x02, 0x03, 0x04, 0x05, 0x05, 0x06, 0x07, 0x09,
0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0x10, 0x12, 0x14, 0x15, 0x17, 0x19, 0x1B, 0x1D, 0x1F, 0x21, 0x23,
0x25, 0x27, 0x2A, 0x2C, 0x2E, 0x31, 0x33, 0x36, 0x38, 0x3B, 0x3E, 0x40, 0x43, 0x46, 0x49, 0x4C,
0x4E, 0x51, 0x54, 0x57, 0x5A, 0x5D, 0x60, 0x63, 0x66, 0x69, 0x6C, 0x6F, 0x73, 0x76, 0x79, 0x7C,
0x7F, 0x82, 0x85, 0x88, 0x8B, 0x8F, 0x92, 0x95, 0x98, 0x9B, 0x9E, 0xA1, 0xA4, 0xA7, 0xAA, 0xAD,
0xB0, 0xB2, 0xB5, 0xB8, 0xBB, 0xBE, 0xC0, 0xC3, 0xC6, 0xC8, 0xCB, 0xCD, 0xD0, 0xD2, 0xD4, 0xD7,
0xD9, 0xDB, 0xDD, 0xDF, 0xE1, 0xE3, 0xE5, 0xE7, 0xE9, 0xEA, 0xEC, 0xEE, 0xEF, 0xF0, 0xF2, 0xF3,
0xF4, 0xF5, 0xF7, 0xF8, 0xF9, 0xF9, 0xFA, 0xFB, 0xFC, 0xFC, 0xFD, 0xFD, 0xFD, 0xFE, 0xFE, 0xFE,
0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xFD, 0xFD, 0xFC, 0xFC, 0xFB, 0xFA, 0xF9, 0xF9, 0xF8, 0xF7, 0xF5,
0xF4, 0xF3, 0xF2, 0xF0, 0xEF, 0xEE, 0xEC, 0xEA, 0xE9, 0xE7, 0xE5, 0xE3, 0xE1, 0xDF, 0xDD, 0xDB,
0xD9, 0xD7, 0xD4, 0xD2, 0xD0, 0xCD, 0xCB, 0xC8, 0xC6, 0xC3, 0xC0, 0xBE, 0xBB, 0xB8, 0xB5, 0xB2,
0xB0, 0xAD, 0xAA, 0xA7, 0xA4, 0xA1, 0x9E, 0x9B, 0x98, 0x95, 0x92, 0x8F, 0x8B, 0x88, 0x85, 0x82,
};Будем использовать режим phase correct PWM, поскольку он дает наименьшие искажения (уровень гармоник) выходного синуса.
В этом режиме таймер считает от 0 до 0xFF, затем до 0 и снова до 0xFF ( 0x00, 0x01, ... 0xFE, 0xFF, 0xFE, ... 0x01, 0x00, 0x01 ). Можно заметить, что за каждый цикл таймер проходит дважды каждое значение кроме 0x00 и 0xFF (их по 1 разу). Поэтому полный цикл будет не 512 тиков, а 510.
В прерывании таймера мы будем расчитывать очередную фазу сигнала, расчитывать из этой фазы индекс массива, брать очередное значение из массива и класть его в регистр OCR0A.
Если мы будем выбирать из этого массива значения подряд (индекс будет увеличиваться на 1 в каждом прерывании), то получим выходную частоту, равную T0CLK/510/256. Если мы будем увеличивать индекс на 2, то весь массив будет пройден вдвое быстрее, т.е. получим частоту (T0CLK/510/256)*2, если будем увеличивать индекс на 3, то получим частоту (T0CLK/510/256)*3. Итого, шаг перестройки частоты будет (T0CLK/510/256), что для кварца 4МГц даст минимальную частоту и шаг перестройки примерно 30.6 Гц. Максимальная частота, согласно Котельникову, будет при выборке двух точек на период и составит (T0CLK/510/256) * 127 =~ 3891Гц. Чтобы ее увеличить, надо или увеличить частоту кварца или уменьшить период таймера (скажем, чтобы он считал не до 0xFF, а до 0x80) и соответствующим образом скорректировать значения в массиве. А что делать, если мы хотим получить частоту, скажем T0CLK/510/256 * 1.5? Можно было бы использовать арифметику с плавающей точкой, но она требует слишком больших вычислительных ресурсов, а у нас есть только 510 тактов между прерываниями таймера. Нам на выручку придет арифметика с фиксированной точкой. Мы возьмем двухбайтовую переменную в качестве счетчика (аккумулятора) фазы и будем считать старший байт целой частью, а младший - дробной частью. Подробнее о числах с фиксированной точкой можно почитать тут: compgraphics.info/2D/fixed.php. Целая часть аккумулятора фазы будет играть роль индекса массива. Приняв такой формат, мы можем изменять индекс с шагом 1/256 и, соответственно, получим минимальную частоту и шаг перестройки (T0CLK/510/256)/256 =~ 0.12Гц, а максимальную - (T0CLK/510/256/256)*(127*256 + 255)= ~3921Гц
#define FREQUENCY 1234.5 // Hz
#define T0CLK F_CPU
//#define PHASE_STEP (uint16_t)((FREQUENCY) / (T0CLK / 510 / 256) * 256)
// последнее * 256 - перевод в формат с фиксированной точкой 8.8
// Слегка перегруппируем формулу, чтобы избежать ошибок округления при вычислении в целых числах.
// Сделаем один из множителей типа unsigned long long, чтобы при целом FREQUENCY все выражение вычислялось в unsigned long long.
// В противном случае выражение будет вычисляться в int и возникнет переполнение.
#define PHASE_STEP (uint16_t)(((FREQUENCY) * 256ULL * 256 * 510) / T0CLK)
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
int main()
{
// настраиваем таймер на phase correct PWM, c максимальным значением 0xFF, на максимальной частоте.
// ШИМ на OCR0B будет формироваться в режиме Clear OC0A on Compare Match when up-counting,
// Set OC0A on Compare Match when down-counting.
TCCR0A = (1<<COM0A1)|(0<<COM0A0)|(0<<COM0B1)|(0<<COM0B0)|(1<<WGM01)|(1<<WGM00);
TCCR0B = (0<<FOC0A)|(0<<FOC0B)|(0<<WGM02)|(0<<CS02)|(0<<CS01)|(1<<CS00);
// Обновлять регистр ШИМ будем в прерывании по переполнению таймера
TIMSK0 = (1<<TOIE0);
// Настраиваем вывод OC0A (PD6) на выход.
DDRD = (0<<7)|(1<<6)|(0<<5)|(0<<4)|(0<<3)|(0<<2)|(0<<1)|(0<<0);
sei(); // разрешаем прерывания
for(;;)
{
// все происходит в прерывании
wdt_reset();
}
}
// добавим вспомогательную функцию получения значения ШИМ для конкретной фазы. Пока она простая, потом мы ее улучшим:
static inline uint8_t get_sinus(uint8_t phase)
{
return pgm_read_byte(&sin_table[phase]);
}
// Объявляем переменную аккумулятора фазы, ее старший байт будет использоваться как индекс в таблице.
// Переменная используется _только_в_одном_ прерывании, поэтому объявляем ее без квалификатора volatile:
uint16_t Sin_phase;
// и, собственно, прерывание.
ISR(TIMER0_OVF_vect)
{
// Увеличиваем фазу.
Sin_phase += PHASE_STEP;
// И используем старший байт как индекс массива.
OCR0A = get_sinus(Sin_phase >> 8);
}Завтра постараюсь написать, как можно почти в 4 раза уменьшить таблицу.
P.S. А что же этот форум так корежит исходники по ширине?
|